/**
 * 
 */
package jmine.tec.utils.collection.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jmine.tec.utils.collection.Dropable;

/**
 * Este nó possui um <i>Dropable< T ></i> que serve de comparador par permitir ou não o "escorrimento" de outros nós.
 * <p>
 * Seus "dutos" filhos são armazenados em uma lista e seu <i>pool</i> é um <i>Set</i>.
 * <p>
 * Todo nó possui um <i>sink</i>, mas apenas o nó cabeça irá utilizá-lo.
 * 
 * @author gigante
 * @param <T> o parâmetro para dropable
 */
public class DropNode<T> {

    private List<DropNode<T>> nodes = new ArrayList<DropNode<T>>();

    private Dropable<T> comparator;

    private Set<Dropable<T>> pool = new HashSet<Dropable<T>>();

    private List<Dropable<T>> sink;

    /**
     * @param t o comparator para este node. Se t for <code>null</code>, então este é um nó cabeça.
     */
    public DropNode(final Dropable<T> t) {
        this.comparator = t;
    }

    /**
     * Adiciona na lista de nós que não escorreram por nenhum outro nó.
     * 
     * @param trash o dropable a ser adicionado
     */
    public void addToSink(final Dropable<T> trash) {
        if (this.sink == null) {
            this.sink = new ArrayList<Dropable<T>>();
        }
        this.sink.add(trash);
    }

    /**
     * Verifica se o dropable é inserível neste nó. Caso seja, então ele é inserido no pool.
     * 
     * @param toComp o dropable a ser inserido
     * @return true se foi inserido com sucesso, false c.c.
     */
    public boolean allowAndInsert(final Dropable<T> toComp) {
        if (toComp != null && toComp.dropThrough(this.comparator.getEntity())) {
            for (DropNode<T> son : this.nodes) {
                if (son.allowAndInsert(toComp)) {
                    return true;
                }
            }
            return this.pool.add(toComp);
        }
        return false;
    }

    /**
     * Insere um novo nó "duto" na árvore
     * 
     * @param t o nó a ser inserido
     */
    public void insertNode(final Dropable<T> t) {
        for (DropNode<T> son : this.nodes) {
            if (son.allowNode(t)) {
                son.insertNode(t);
                return;
            }
        }
        this.nodes.add(new DropNode<T>(t));
    }

    /**
     * @param t o node a ser verificado
     * @return true se o nó "escorre" através deste nó.
     */
    private boolean allowNode(final Dropable<T> t) {
        if (this.comparator == null) {
            return false;
        }
        return t.dropThrough(this.comparator.getEntity());
    }

    /**
     * @param toAdd o dropable a ser adicionado
     */
    public void addToPool(final Dropable<T> toAdd) {
        this.pool.add(toAdd);
    }

    /**
     * @param d o nó para recuperar os filhos
     * @return a lista com os nós "dutos" filhos
     */
    public List<Dropable<T>> getNodesFrom(final Dropable<T> d) {
        for (DropNode<T> node : this.nodes) {
            if (d.getEntity().equals(node.getComparator().getEntity())) {
                return this.createList(node.getNodes());
            } else if (d.dropThrough(node.getComparator().getEntity())) {
                return node.getNodesFrom(d);
            }
        }
        return null;
    }

    /**
     * @param d o node
     * @return a lista com os valores no pool de d
     */
    public List<Dropable<T>> getValuesOf(final Dropable<T> d) {
        if (d == null) {
            return this.createListDrop(this);
        }
        for (DropNode<T> node : this.nodes) {
            if (d.getEntity().equals(node.getComparator().getEntity())) {
                return this.createListDrop(node);
            } else if (d.dropThrough(node.getComparator().getEntity())) {
                return node.getValuesOf(d);
            }
        }
        return new ArrayList<Dropable<T>>();
    }

    /**
     * @param node o node com os valores
     * @return a lista com o pool do node passado
     */
    private List<Dropable<T>> createListDrop(final DropNode<T> node) {
        return new ArrayList<Dropable<T>>(node.getPool());
    }

    /**
     * @param sons os filhos deste nó
     * @return a lista com os parâmetros dos nós
     */
    private List<Dropable<T>> createList(final List<DropNode<T>> sons) {
        List<Dropable<T>> ret = new ArrayList<Dropable<T>>();
        for (DropNode<T> node : sons) {
            ret.add(node.getComparator());
        }
        return ret;
    }

    /**
     * Devolve a list com os sets não vazios de cada node
     * 
     * @param list a lista com os sets
     */
    public void setsAsList(final List<Collection<Dropable<T>>> list) {
        for (DropNode<T> node : this.nodes) {
            node.setsAsList(list);
        }
        if (!this.pool.isEmpty()) {
            list.add(this.pool);
        }
    }

    /**
     * @return String
     */
    @Override
    public String toString() {
        return "Comparador: " + this.comparator.getEntity() + ", filhos: " + this.nodes.size() + ", resto: "
                + (this.sink == null ? 0 : this.sink.size());
    }

    /**
     * @return os nós filhos desse nó.
     */
    public List<DropNode<T>> getNodes() {
        return this.nodes;
    }

    /**
     * @param nodes os nós filhos desse nó.
     */
    public void setNodes(final List<DropNode<T>> nodes) {
        this.nodes = nodes;
    }

    /**
     * @return o pool com os comparables
     */
    public Set<Dropable<T>> getPool() {
        return this.pool;
    }

    /**
     * @return the comparator
     */
    public Dropable<T> getComparator() {
        return this.comparator;
    }

    /**
     * @return the sink
     */
    public List<Dropable<T>> getSink() {
        return this.sink;
    }
}
